﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Reflection;

using Compiler.TableOfSymbols;
using Compiler.TableOfSymbols.Types;

namespace Compiler.CompilerAPI.Contexts
{
    /// <summary>
    /// Глобальный контекст программы
    /// </summary>
    public class GlobalContext : Context
    {
        private static string[] defaultDLLs = new string[] { "mscorlib.dll" };
        private LinkedList<Assembly> usedDLLs;

        private HashSet<string> availableNamespaces;
        private HashSet<string> usedNamespaces;

        private SymbolTable symbolTable;

        public GlobalContext(NETTypesBijectiveMap typesBijectiveMap, HashSet<DllLibInfo> usedDLLs)
            : base(typesBijectiveMap)
        {
            if (usedDLLs == null)
                throw new ArgumentNullException("usedDLLs can not be null");
            var usedDLLNames = new LinkedList<string>(
                usedDLLs.Where(lib => lib.Exists).Select(lib => lib.FileName));
            _LoadAssemblies(usedDLLNames);
            usedNamespaces = new HashSet<string>();
            symbolTable = new SymbolTable();
        }

        public bool NamespaceIsAvailable(string namespaceName)
        {
            System.Diagnostics.Debug.Assert((namespaceName != null) && (namespaceName != ""),
                String.Format("Namespace name can not be empty"));
            return availableNamespaces.Contains(namespaceName);
        }

        public void UseNamespace(string namespaceName)
        {
            System.Diagnostics.Debug.Assert((namespaceName != null) && (namespaceName != ""),
                String.Format("Namespace name can not be empty"));
            System.Diagnostics.Debug.Assert(availableNamespaces.Contains(namespaceName),
                String.Format("Used namespace must be available"));
            usedNamespaces.Add(namespaceName);
        }

        /// <summary>
        /// Проверяет, определена ли в контексте какая-нибудь 
        /// структурная единица с именем name
        /// </summary>
        /// <param name="name">Имя</param>
        /// <returns>Истину, если в контексте определена единица с именем name</returns>
        public override bool ContainsDefinedName(string name)
        {
            System.Diagnostics.Debug.Assert((name != null) && (name != ""),
                String.Format("name can not be empty"));
            return symbolTable.Contains(name);
        }

        /// <summary>
        /// Добавляет в контекст определение новой структурной единицы
        /// </summary>
        /// <param name="name">Имя новой единицы</param>
        /// <param name="data">Данные структурной единицы</param>
        public override void AddNewDefinition(string name, SymbolTableData data)
        {
            System.Diagnostics.Debug.Assert((name != null) && (name != ""),
                String.Format("name can not be empty"));
            System.Diagnostics.Debug.Assert(data != null,
                String.Format("data can not be null"));
            System.Diagnostics.Debug.Assert(!symbolTable.Contains(name),
                String.Format("name must be new (unused in context)"));
            symbolTable.Add(name, data);
        }

        /// <summary>
        /// Ищет в контексте информацию о типе
        /// </summary>
        /// <param name="name">Имя типа</param>
        /// <param name="qualifier">Квалификатор пространств имён</param>
        /// <returns>Информацию о результатах поиска (включает тип, если найден)</returns>
        public override TypeSearchInfo TryGetTypeInfo(string name, string qualifier)
        {
            HashSet<Type> possibleTypes = new HashSet<Type>();
            // generic types have names: A`2, B`1 (after ` -- type params count)
            foreach (Assembly assembly in usedDLLs)
                possibleTypes.UnionWith(assembly.GetTypes().Where(
                    t => t.Name == name));
            if (qualifier == "")
                possibleTypes = new HashSet<Type>(
                    possibleTypes.Where((Type t) => usedNamespaces.Contains(t.Namespace)));
            else
            {
                var fullNamespaces = usedNamespaces.Select(
                    nspaceName => (nspaceName + "." + qualifier));
                fullNamespaces = fullNamespaces.Union(new string[1] { qualifier });
                possibleTypes = new HashSet<Type>(
                    possibleTypes.Where((Type t) => fullNamespaces.Contains(t.Namespace)));
            }
            if (possibleTypes.Count == 0)
                return new TypeSearchInfo(true, false);
            else if (possibleTypes.Count > 1)
                return new TypeSearchInfo(false, true);
            else
            {
                Type NETTypeInfo = possibleTypes.First();
                BaseType typeInfo;
                if (TypesBijectiveMap.Contains(NETTypeInfo))
                    typeInfo = TypesBijectiveMap[NETTypeInfo];
                else
                {
                    typeInfo = this.TypesBijectiveMap.MakeTypeInfo(NETTypeInfo);
                    TypesBijectiveMap.AddPair(typeInfo, NETTypeInfo);
                }
                return new TypeSearchInfo(typeInfo);
            }
        }

        private void _LoadAssemblies(IEnumerable<string> usedDLLNames)
        { 
            usedDLLs = new LinkedList<Assembly>();
            availableNamespaces = new HashSet<string>();
            foreach (string assemblyName in defaultDLLs)
            {
                var newAssembly = Assembly.Load(assemblyName);
                usedDLLs.AddLast(newAssembly);
                availableNamespaces.UnionWith(
                    newAssembly.GetTypes().Select(t => t.Namespace).Distinct());
            }
            foreach (string assemblyFileName in usedDLLNames)
            {
                var newAssembly = Assembly.LoadFrom(assemblyFileName);
                usedDLLs.AddLast(newAssembly);
                availableNamespaces.UnionWith(
                    newAssembly.GetTypes().Select(t => t.Namespace).Distinct());
            }
        }
    }
}
